﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace Jack.RedisLib
{
    public partial class RedisClient
    {
        public async Task<bool> GeoAdd(string key, string member, double longitude, double latitude, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.GeoAddAsync(redisKey, longitude, latitude, member);
        }

        public async Task<long> GeoAdd(string key, List<(string Member, double Longitude, double Latitude)> geos, int db = -1, CancellationToken cancellationToken = default)
        {
            if (geos == null || geos.Count == 0) return 0;

            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            GeoEntry[] geoEntries = new GeoEntry[geos.Count];
            int i = 0;
            foreach (var (Member, Longitude, Latitude) in geos)
            {
                GeoEntry geoEntry = new(Longitude, Latitude, Member);
                geoEntries[i++] = geoEntry;
            }

            return await database.GeoAddAsync(redisKey, geoEntries);
        }

        public async Task<bool> GeoRemove(string key, string member, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.GeoRemoveAsync(redisKey, member);
        }

        public async Task<double?> GeoDistance(string key, string member1, string member2, GeoUnit geoUnit = GeoUnit.Meters, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.GeoDistanceAsync(redisKey, member1, member2, geoUnit);
        }

        public async Task<string> GeoHash(string key, string member, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.GeoHashAsync(redisKey, member);
        }

        public async Task<string[]> GeoHash(string key, List<string> members, int db = -1, CancellationToken cancellationToken = default)
        {
            if (members == null || members.Count == 0) return [];

            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = new RedisValue[members.Count];
            int i = 0;
            foreach (string member in members)
            {
                redisValues[i++] = member;
            }

            return await database.GeoHashAsync(redisKey, redisValues);
        }

        public async Task<(double Latitude, double Longitude)?> GeoPosition(string key, string member, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            GeoPosition? geoPosition = await database.GeoPositionAsync(redisKey, member);
            if (geoPosition.HasValue == false) return default;

            return new(geoPosition.Value.Latitude, geoPosition.Value.Longitude);
        }

        public async Task<(double Latitude, double Longitude)?[]> GeoPosition(string key, List<string> members, int db = -1, CancellationToken cancellationToken = default)
        {
            if (members == null || members.Count == 0) return [];

            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = new RedisValue[members.Count];
            int i = 0;
            foreach (string member in members)
            {
                redisValues[i++] = member;
            }

            GeoPosition?[] geoPositions = await database.GeoPositionAsync(redisKey, redisValues);
            if (geoPositions == null || geoPositions.Length == 0) return [];
            (double Latitude, double Longitude)?[] positions = new (double Latitude, double Longitude)?[geoPositions.Length];
            for (i = 0; i < geoPositions.Length; i++)
            {
                var geoPosition = geoPositions[i];
                if (geoPosition.HasValue == false) continue;

                positions[i] = new(geoPosition.Value.Latitude, geoPosition.Value.Longitude);
            }

            return positions;
        }

        public async Task<(string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[]> GeoRadius(string key, string member, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            GeoRadiusResult[] geoRadiusResults = await database.GeoRadiusAsync(redisKey, member, radius, unit, count, order, options);
            if (geoRadiusResults == null || geoRadiusResults.Length == 0) return [];

            (string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[] geoRadius = new (string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[geoRadiusResults.Length];
            for (int i = 0; i < geoRadiusResults.Length; i++)
            {
                var geoRadiusResult = geoRadiusResults[i];

                geoRadius[i] = new(geoRadiusResult.Member, geoRadiusResult.Distance, geoRadiusResult.Hash, geoRadiusResult.Position?.Latitude, geoRadiusResult.Position?.Longitude);
            }

            return geoRadius;
        }

        public async Task<(string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[]> GeoRadius(string key, double longitude, double latitude, double radius, GeoUnit unit = GeoUnit.Meters, int count = -1, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            GeoRadiusResult[] geoRadiusResults = await database.GeoRadiusAsync(redisKey, longitude, latitude, radius, unit, count, order, options);
            if (geoRadiusResults == null || geoRadiusResults.Length == 0) return [];

            (string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[] geoRadius = new (string Member, double? Distance, long? Hash, double? Latitude, double? Longitude)[geoRadiusResults.Length];
            for (int i = 0; i < geoRadiusResults.Length; i++)
            {
                var geoRadiusResult = geoRadiusResults[i];

                geoRadius[i] = new(geoRadiusResult.Member, geoRadiusResult.Distance, geoRadiusResult.Hash, geoRadiusResult.Position?.Latitude, geoRadiusResult.Position?.Longitude);
            }

            return geoRadius;
        }
    }
}
